package cc.bukkitPlugin.banitem.check;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.commons.lang.StringUtils;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;

import cc.bukkitPlugin.banitem.BanItem;
import cc.bukkitPlugin.banitem.BanItemEnume.CheckedType;
import cc.bukkitPlugin.banitem.BanItemEnume.ListType;
import cc.bukkitPlugin.banitem.api.ModAPIMan;
import cc.bukkitPlugin.banitem.manager.ItemManager;
import cc.bukkitPlugin.banitem.manager.PermissionMan;
import cc.bukkitPlugin.banitem.util.BINMSUtil;
import cc.bukkitPlugin.util.CCFunction;
import cc.bukkitPlugin.util.ClassUtil;
import cc.bukkitPlugin.util.config.CommentedYamlConfig;
import cc.bukkitPlugin.util.nbt.NBTUtil;
import cc.bukkitPlugin.util.plugin.ABukkitPlugin;
import cc.bukkitPlugin.util.plugin.AManager;
import cc.bukkitPlugin.util.plugin.INeedConfig;

public class ItemCheck extends AManager<BanItem> implements INeedConfig{

    private static final HashSet<String> mIdKeys=new HashSet<>(CCFunction.asList(new String[]{"id","type"}));
    private static final HashSet<String> mDamageKeys=new HashSet<>(CCFunction.asList(new String[]{"damage","meta"}));
    
    private boolean mForceConfiscateWhenCheckInv=true;
    private boolean mCheckBanItemInBlockNBT=true;
    @Deprecated
    private ItemManager mItemManager;
    @Deprecated
    private PermissionMan mPermissionMan;

    /**
     * 根据字符串获取材料类型
     * @param value 字符串
     * @return 肯定不为null
     */
    private static Material getMaterial(String value){
        value=value.toUpperCase();
        if(value.startsWith("MINECRAFT:"))
            value=value.substring(10);
        else value=value.replace(':','_');
        Material material=Material.getMaterial(value);
        if(material==null)
            return Material.AIR;
        return material;
    }

    public ItemCheck(BanItem pPlugin){
        super(pPlugin);
        this.mPlugin.registerConfigModel(this);
    }

    @Override
    public void setConfig(CommandSender pSender){
        CommentedYamlConfig tConfig=this.mPlugin.getConfigManager().getConfig();
        this.mForceConfiscateWhenCheckInv=tConfig.getBoolean("BanItem.ForceConfiscateWhenCheckInv",true);
        this.mCheckBanItemInBlockNBT=tConfig.getBoolean("BanItem.CheckBanItemInBlockNBT",true);

        ABukkitPlugin.info(pSender,">检查背包时强制没收黑名单物品: "+this.mForceConfiscateWhenCheckInv);
        ABukkitPlugin.info(pSender,">在方块NBT中检查黑名单物品: "+this.mCheckBanItemInBlockNBT);
    }

    private PermissionMan getPermissionMan(){
        if(this.mPermissionMan==null){
            this.mPermissionMan=this.mPlugin.getManager(PermissionMan.class);
        }
        return this.mPermissionMan;
    }

    private ItemManager getItemManager(){
        if(this.mItemManager==null){
            this.mItemManager=this.mPlugin.getManager(ItemManager.class);
        }
        return this.mItemManager;
    }

    /**
     * 检查指定的物品是否与指定的列表中的至少一项配置匹配
     * <p>
     * 检查结构存放在{@link CheckResult#mFind}中
     * 检查类型存放在{@link CheckResult#mCheckedType}中
     * 禁用原因存放在{@link CheckResult#mReason}中
     * </p>
     * @param pBukkitItem 要检查的物品,可以为null
     * @param pCheckType 检查的列表类型
     * @return 检查结果
     */
    public CheckResult checkExist(ItemStack pBukkitItem,String pWorld,ListType pCheckType){
        if(pBukkitItem==null||pBukkitItem.getType()==Material.AIR)
            return CheckResult.notFind();

        Material id=pBukkitItem.getType();
        short damage=pBukkitItem.getDurability();

        ItemManager tItemMan=this.getItemManager();
        SimpleWorld tWorld=SimpleWorld.create(pWorld);
        WorldTypeItem tWorldTypeItem=tItemMan.getWorldTypeItem(tWorld,pCheckType);
        // 检查物品本身是否禁用
        DamageList damages=tWorldTypeItem.getDamageList(id);
        if(damages!=null){
            HashSet<Damage> checkDamages=new HashSet<>();
            Damage td=damages.get((short)-1);
            if(td!=null){
                if(td.isEmpty())
                    return CheckResult.find(td.getReason());
                else checkDamages.add(td);
            }
            td=damages.get(damage);
            if(td!=null){
                if(td.isEmpty())
                    return CheckResult.find(td.getReason());
                else checkDamages.add(td);
            }
            Object tag=NBTUtil.getNBTTagCompound(pBukkitItem);
            if((td=this.simpleCheckNBTExist(tag,checkDamages))!=null)
                return CheckResult.find(td.getReason());
        }
        // 如果在NBT检查列表中,检查物品的NBT
        DamageList checkDamageList=tItemMan.getWorldTypeItem(tWorld,ListType.NBT).getDamageList(id);
        if(checkDamageList!=null&&(checkDamageList.containsKey((short)-1)||(checkDamageList.containsKey(damage)))){
            Object tNMSItem=BINMSUtil.getNMSItem(pBukkitItem);
            Object tNBTTagCompound=null;
            if(tNMSItem!=null&&(tNBTTagCompound=ClassUtil.getFieldValue(tNMSItem,BINMSUtil.field_NMSItemStack_tag))!=null){
                CheckResult tCheckResult=isListItemInNBT(tNBTTagCompound,pCheckType,0,tWorldTypeItem);
                if(tCheckResult.mFind){// 如果在NBT中查询发现物品,直接返回
                    if(!this.isRemoveItemFromNBT(pCheckType))
                        return tCheckResult;
                    ClassUtil.setFieldValue(tNMSItem,BINMSUtil.field_NMSItemStack_tag,tNBTTagCompound);
                    tCheckResult.mHandledItem=(ItemStack)ClassUtil.invokeStaticMethod(BINMSUtil.method_CraftItemStack_asCraftMirror,tNMSItem);
                    return tCheckResult;
                }
            }
        }
        return CheckResult.notFind(); // 不存在对应的damage
    }

    /**
     * 检查指定的方块是否与指定的列表中的至少一项配置匹配
     * <p>
     * 检查结构存放在{@link CheckResult#mFind}中
     * 检查类型存放在{@link CheckResult#mCheckedType}中
     * 禁用原因存放在{@link CheckResult#mReason}中
     * </p>
     * @param pBukkitItem 要检查的物品,可以为null
     * @param pCheckType 检查的列表类型
     * @return 检查结果
     */
    public CheckResult checkExist(Block pBukkitBlock,String pWorld,ListType pCheckType){
        if(pBukkitBlock==null||pBukkitBlock.getType()==Material.AIR)
            return CheckResult.notFind();
        Material id=pBukkitBlock.getType();
        short damage=pBukkitBlock.getData();

        ItemManager tItemMan=this.getItemManager();
        SimpleWorld tWorld=SimpleWorld.create(pWorld);
        WorldTypeItem tWorldTypeItem=tItemMan.getWorldTypeItem(tWorld,pCheckType);
        DamageList damages=tWorldTypeItem.getDamageList(id);
        if(damages!=null){ //检查自身
            HashSet<Damage> checkDamages=new HashSet<>();
            Damage td=damages.get((short)-1);
            if(td!=null){
                if(td.isEmpty())
                    return CheckResult.find(td.getReason());
                else checkDamages.add(td);
            }
            td=damages.get(damage);
            if(td!=null){
                if(td.isEmpty())
                    return CheckResult.find(td.getReason());
                else checkDamages.add(td);
            }
            if(!checkDamages.isEmpty()){
                Object tag=BINMSUtil.getNBTTagCompound(pBukkitBlock);
                if((td=this.simpleCheckNBTExist(tag,checkDamages))!=null)
                    return CheckResult.find(td.getReason());
            }
        }
        if(this.mCheckBanItemInBlockNBT){
            // 如果在NBT检查列表中,检查物品的NBT
            DamageList checkDamageList=tItemMan.getWorldTypeItem(tWorld,ListType.NBT).getDamageList(id);
            if(checkDamageList!=null&&(checkDamageList.containsKey((short)-1)||(checkDamageList.containsKey(damage)))){
                Object tNMSTileEntity=BINMSUtil.getTileEntity(pBukkitBlock);
                Object tNBTTagCompound=null;
                if(tNMSTileEntity!=null&&(tNBTTagCompound=BINMSUtil.getTileEntityNBT(tNMSTileEntity,true))!=null){
                    CheckResult tCheckResult=isListItemInNBT(tNBTTagCompound,pCheckType,0,tWorldTypeItem);
                    if(tCheckResult.mFind){// 如果在NBT中查询发现物品,直接返回
                        if(!this.isRemoveItemFromNBT(pCheckType))
                            return tCheckResult;
                        ClassUtil.invokeMethod(tNMSTileEntity,BINMSUtil.method_NMSTileEntity_readFromNBT,tNBTTagCompound);
                        return tCheckResult;
                    }
                }
            }
        }

        return CheckResult.notFind();
    }

    /**
     * 检查一个玩家的所有背包
     * <p>
     * 此函数会检查玩家是否有权跳过该检查
     * </p>
     * @param pPlayer 被检查的玩家
     * @param pSender 发送检查消息给谁,如果为null,默认发送给控制台
     */
    public void checkPlayerInv(Player pPlayer,CommandSender pSender){
        if(pPlayer==null)
            return;
        if(pSender==null)
            pSender=Bukkit.getConsoleSender();
        if(this.getPermissionMan().hasHighPermission(pPlayer,ListType.BAN)){
            ABukkitPlugin.send(pSender,"玩家["+pPlayer.getName()+"]拥有免检查权限,跳过检查背包");
            return;
        }
        HashSet<Inventory> checkInvs=new HashSet<>();
        checkInvs.add(pPlayer.getInventory()); // 玩家背包
        checkInvs.addAll(ModAPIMan.getModInventory(pPlayer));// 其他mod背包....
        int checkedCount=0,confiscateCount=0,permitIgnoreCount=0;
        CheckResult tCheckResult=null;
        for(Inventory sInv : checkInvs){
            int tInvSize=sInv.getSize();
            if(sInv.getClass().getSimpleName().equals("CraftInventoryPlayer"))
                tInvSize+=4;
            for(int i=0;i<tInvSize;i++){
                ItemStack sItem=sInv.getItem(i);
                if(sItem==null||sItem.getType()==Material.AIR)
                    continue;
                if(!(tCheckResult=this.checkExist(sItem,pPlayer.getWorld().getName(),ListType.BAN)).mFind)
                    continue;
                checkedCount++;
                if(this.getPermissionMan().hasPermission(pPlayer,sItem,ListType.BAN)){
                    permitIgnoreCount++;
                    continue;
                }
                if(tCheckResult.mCheckedType==CheckedType.NBT&&this.isRemoveItemFromNBT(ListType.BAN)){
                    sInv.setItem(i,tCheckResult.mHandledItem);
                }else if(this.mForceConfiscateWhenCheckInv||this.mPlugin.getConfigManager().isSetNull()){
                    sInv.setItem(i,null);
                    confiscateCount++;
                }else continue;
                ABukkitPlugin.send(pPlayer,tCheckResult.mReason);
            }
        }
        ABukkitPlugin.send(pSender,"检查了玩家"+pPlayer.getName()+"的"+checkInvs.size()+"个背包");
        ABukkitPlugin.send(pSender,"发现[黑名单]物品"+checkedCount+"件,权限忽略没收"+permitIgnoreCount+"件,没收了"+confiscateCount+"件");
    }

    /**
     * 检查指定的nbt项是否包含指定Damage列表中nbts里的值
     * <p>
     * 如果存在某项damage的NBT检查列表为Empty时则存在
     * </p>
     * @param tNBTTagCompound_tag 要检查的NBT,可以为null
     * @param pCheckDamages 存储所有该物品被禁用的damage列表
     * @return 如果存在,返回存在的那个Damage,不存在则为null
     */
    private Damage simpleCheckNBTExist(Object tNBTTagCompound_tag,Set<Damage> pCheckDamages){
        if(pCheckDamages.isEmpty())
            return null; // 不存在对应物品的damage列表
        Map<String,Object> tNBTContents=NBTUtil.getNBTTagMapFromTag(tNBTTagCompound_tag);
        for(Damage sCheckDamage : pCheckDamages){
            if(sCheckDamage.isEmpty())
                return sCheckDamage; // damage存在且无NBT约束
            if(tNBTContents==null||tNBTContents.isEmpty())
                continue; //如果NBT为null或空
            for(String sNBTLabel : sCheckDamage.getNBTLabelSet()){
                Object nbtValue=tNBTContents.get(sNBTLabel);
                if(nbtValue==null)
                    continue;
                if(sCheckDamage.contains(sNBTLabel,NBTUtil.getNBTSimpleJson(nbtValue)))
                    return sCheckDamage;
            }
        }
        //不存在对应的damage
        return null;
    }
    
    private boolean isKeyOfId(String pKey){
        if(StringUtils.isEmpty(pKey)) return false;
        return ItemCheck.mIdKeys.contains(pKey.toLowerCase());
    }
    
    private boolean isKeyOfDamage(String pKey){
        if(StringUtils.isEmpty(pKey)) return false;
        return ItemCheck.mDamageKeys.contains(pKey.toLowerCase());
    }

    public boolean isRemoveItemFromNBT(ListType pListType){
        if(pListType==ListType.BAN&&this.mPlugin.getConfigManager().isRemoveBanedItemFromNBT())
            return true;
        if(pListType==ListType.USE&&this.mPlugin.getConfigManager().isRemoveNoUseItemFromNBT())
            return true;
        return false;
    }

    /**
     * 检查指定的NBT中是否存在物品与指定的列表中的至少一项配置匹配
     * <p>
     * 检查结构存放在{@link CheckResult#mFind}中
     * 检查类型存放在{@link CheckResult#mCheckedType}中
     * 禁用原因存放在{@link CheckResult#mReason}中
     * </p>
     * @param pNBTBase_target 要检查的NBT,可以为null
     * @param list 
     * @param pWorldTypeItem 检查的列表
     * @return 检查结果
     */
    public CheckResult isListItemInNBT(Object pNBTBase_target,ListType pListType,int pDeep,WorldTypeItem pWorldTypeItem){
        if(pNBTBase_target==null)
            return CheckResult.notFind();
        if(BINMSUtil.clazz_NBTTagCompound.isInstance(pNBTBase_target)){
            Map<String,Object> tNBTContents=NBTUtil.getNBTTagMapFromTag(pNBTBase_target);
            if(tNBTContents==null)
                return CheckResult.notFind(); // 不存在NBT值
            /**存储找到的物品的Damage值*/
            HashSet<Short> findDamageValues=new HashSet<>();
            /**存储找到的物品ID对应的Damage列表*/
            HashSet<DamageList> damageNodes=new HashSet<>();
            /**用于首层NBT禁用特殊物品时使用*/
            Object tNBTTagCompound_tagValue=null;
            /**用于广度优先,如果横向搜索到则返回,否则加深*/
            HashMap<String,Object> tagNBTTagCompounds=new HashMap<>();
            for(String sKey : tNBTContents.keySet()){
                Object tNBTContentNode=tNBTContents.get(sKey);
                if(BINMSUtil.clazz_NBTTagCompound.isInstance(tNBTContentNode)){
                    tagNBTTagCompounds.put(sKey,tNBTContentNode);
                    if(sKey.equalsIgnoreCase("tag"))
                        tNBTTagCompound_tagValue=tNBTContentNode;
                }else if(BINMSUtil.clazz_NBTTagList.isInstance(tNBTContentNode)){
                    tagNBTTagCompounds.put(sKey,tNBTContentNode);
                }else if(BINMSUtil.clazz_NBTTagString.isInstance(tNBTContentNode)&&this.isKeyOfId(sKey)){
                    String typeStr=(String)ClassUtil.getFieldValue(tNBTContentNode,BINMSUtil.field_NBTTagString_value);
                    Material tid=ItemCheck.getMaterial(typeStr);
                    if(tid!=Material.AIR){
                        DamageList tdDamageList=pWorldTypeItem.getDamageList(tid);
                        if(tdDamageList!=null)
                            damageNodes.add(tdDamageList);
                    }
                }else if(BINMSUtil.clazz_NBTTagShort.isInstance(tNBTContentNode)){
                    if(this.isKeyOfId(sKey)){
                        Material tid=Material.getMaterial((short)ClassUtil.getFieldValue(tNBTContentNode,BINMSUtil.field_NBTTagShort_value));
                        if(tid!=Material.AIR){
                            DamageList tdDamageList=pWorldTypeItem.getDamageList(tid);
                            if(tdDamageList!=null)
                                damageNodes.add(tdDamageList);
                        }
                    }
                    if(this.isKeyOfDamage(sKey)){
                        short tdamage=(short)ClassUtil.getFieldValue(tNBTContentNode,BINMSUtil.field_NBTTagShort_value);
                        if(tdamage>=0)
                            findDamageValues.add(tdamage);
                    }
                }
            }

            if(!damageNodes.isEmpty()&&!findDamageValues.isEmpty()){ // 本层发现物品,检查是否为禁用物品,如果不是,尝试深度搜索
                findDamageValues.add((short)-1); //添加全局-1
                for(DamageList damageNode : damageNodes){
                    HashSet<Damage> checkDamages=new HashSet<>();
                    for(short sDamageValue : findDamageValues){
                        Damage td=damageNode.get(sDamageValue);
                        if(td!=null)
                            checkDamages.add(td);
                    }
                    Damage td=this.simpleCheckNBTExist(tNBTTagCompound_tagValue,checkDamages);
                    if(td!=null)
                        return CheckResult.find(pDeep,td.getReason());
                }
            }
            for(Entry<String,Object> entry : tagNBTTagCompounds.entrySet()){ // 深度搜索
                CheckResult tResult=this.isListItemInNBT(entry.getValue(),pListType,pDeep+1,pWorldTypeItem);
                if(tResult.mFind){
                    if(this.isRemoveItemFromNBT(pListType)&&pDeep+1==tResult.mDeep) //如果是直接下层找到,且移除物品
                        NBTUtil.removeFromNBTTagCompound(pNBTBase_target,entry.getKey());
                    return tResult;
                }
            }
            return CheckResult.notFind();
        }else if(BINMSUtil.clazz_NBTTagList.isInstance(pNBTBase_target)){
            List<Object> tContent=(List<Object>)ClassUtil.getFieldValue(pNBTBase_target,BINMSUtil.field_NBTTagList_value);
            if(tContent==null)
                return CheckResult.notFind();
            for(Object sNBTBase_content : tContent){
                if(BINMSUtil.clazz_NBTTagCompound.isInstance(sNBTBase_content)||BINMSUtil.clazz_NBTTagList.isInstance(sNBTBase_content)){
                    CheckResult tResult=isListItemInNBT(sNBTBase_content,pListType,pDeep+1,pWorldTypeItem);
                    if(tResult.mFind){
                        if(this.isRemoveItemFromNBT(pListType)&&pDeep+1==tResult.mDeep) //如果是直接下层找到,且移除物品
                            tContent.remove(sNBTBase_content);
                        return tResult;
                    }
                }
            }
            return CheckResult.notFind();
        }
        return CheckResult.notFind();
    }

}
